#!/usr/bin/env python
# -*- coding: utf-8 -*-

import io
import os
import sys
import gzip
import time
import base64
import socket
import sqlite3
import threading
import logging
import logging.handlers
import argparse
from utils.ipip import IP
from utils.sqlitethread import SqliteMultithread

logger = logging.getLogger('honeypot')

IP.load(os.path.join(sys.path[0], 'data/17monipdb.dat'))

g_response_data = None


def gzip_encode(data):
    stream = io.BytesIO()
    gz = gzip.GzipFile(fileobj=stream, mode='wb')
    gz.write(data)
    gz.close()
    base64_data = base64.b64encode(stream.getvalue())
    stream.close()
    return base64_data


def gzip_decode(base64_data):
    gz_data = base64.b64decode(base64_data)
    stream = io.BytesIO(gz_data)
    gz = gzip.GzipFile(fileobj=stream)
    data = gz.read()
    gz.close()
    stream.close()
    return data


def http_response_data():
    # Create response data
    header = (
        'HTTP/1.1 200 OK\r\n'
        'Connection: close\r\n'
        'Content-Length: {0}\r\n'
        'Content-Type: text/html\r\n'
        'Server: Microsoft-IIS/8.5\r\n'
        'X-Powered-By: ASP.NET\r\n'
        'X-Content-Type-Options: nosniff\r\n'
        'X-Frame-Options: deny\r\n'
        'X-XSS-Protection: 1; mode=block\r\n'
        '\r\n'
    )
    content = (
        'H4sIAIc6i1MC/3VSTY/TMBC951fM5tzGIISEihsJLazECQ4ViOM0GTdm/ZE'
        'd2+1GiP/OpGm7QgIf7Kf5eDPzxvru45f73Y+vn2DI3rWVvj6EvTyeMkI3IC'
        'fK27pks35Xiznb7Kj9Tq6LniBHCAcbnu+0WhyVdjY8wsBktvWQ87hR6udTI'
        'Z6KbSRFGTzaLoZGrhryNNK2th4PpJ7Xs70GJiemGQpZytNMCnL2sZ/g1xnO'
        '52T7PGzgzVvy729GjyzdbOAVYMnxxW5iyGuD3rppAzscoscVfCPuMQj4wBb'
        'dChKGtE7E1iyJvyutLvW1uogyNzFL9PpfCoi10mP72cAUCyQS52ATjDLdSi'
        'AtgXCivTj5SAziTaXrKCVTnJvAhpTROeoBQ1+dIj/acGjgobCkM4goxh4KY'
        '7YxzMlMT8Uy9Y1WY1vNxR8iQwyyA4I+dsVTyEu0EEqtcYycYXSEiSTbCGmO'
        'lca/F3bus4l8UHV7w1ph2+g9q7a6j94Td6LajVKawSNah3tHgPk/lPMHuFI'
        'KXiivrWvy7W5A+T2zfEYGKUnGX1STMHGfY7W6bEEtH/YPyD2XPMgCAAA='
    )
    content = gzip_decode(content)
    response_data = header.format(len(content)) + content
    return response_data


def periodic_commit(cursor, seconds):
    while True:
        time.sleep(seconds)
        cursor.commit()


def receiver(conn, addr, cursor):
    try:
        payload = conn.recv(1024)
        if len(payload) > 0:
            logger.info('read  %04i from %s:%d', len(payload), addr[0], addr[1])
            server_addr = conn.getsockname()
            server_ip, server_port = server_addr
            client_ip, client_port = addr
            region = IP.find(client_ip)
            cursor.execute(
                'INSERT INTO honeypot (server_ip, server_port, client_ip, client_port, region, payload)'
                ' VALUES (?, ?, ?, ?, ?, ?)', (
                    server_ip, server_port, client_ip, client_port,
                    region, sqlite3.Binary(payload)
                )
            )
            sent = conn.send(g_response_data)
            logger.info('write %04i to   %s:%d', sent, addr[0], addr[1])
    except socket.error:
        pass
    except Exception as e:
        logger.exception('sqlite insert error: %s', e)
    logger.info('connection from %s:%d is closed.', addr[0], addr[1])
    conn.close()


def honeypot(ip, port):
    # Connect database
    sqlite_db = os.path.join(sys.path[0], 'data', 'db', 'honeypot.db')
    with open(os.path.join(sys.path[0], 'sqls', 'honeypot.sql'), 'rb') as f:
        sql = f.read()
    with sqlite3.connect(sqlite_db) as conn:
        conn.executescript(sql)
    cursor = SqliteMultithread(sqlite_db)

    # Start commit thread
    t = threading.Thread(target=periodic_commit, args=(cursor, 1))
    t.setDaemon(True)
    t.start()

    # Create a TCP/IP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # Bind the socket to the port
    server_address = (ip, port)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(server_address)
    print 'starting up on %s port %s' % server_address

    # Listen for incoming connections
    sock.listen(200)

    while True:
        conn, addr = sock.accept()
        t = threading.Thread(target=receiver, args=(conn, addr, cursor))
        t.setDaemon(True)
        t.start()


def time_converter(secs):
    # Asia/Shanghai
    return time.gmtime(secs + 8 * 60 * 60)


def main():
    global g_response_data
    parser = argparse.ArgumentParser()
    parser.add_argument('-l', '--local-ip', default='0.0.0.0',
                        dest='local_ip', help='local ip address to bind to')
    parser.add_argument('-p', '--local-port', default=8080, type=int,
                        dest='local_port', help='local port to bind to')
    parser.add_argument('-f', type=argparse.FileType('rt'),
                        dest='data_file', help='response data file')
    parser.add_argument('--log', dest='log_file', help='log file')
    opts = parser.parse_args()

    if opts.data_file:
        g_response_data = opts.data_file.read()
        opts.data_file.close()
    else:
        g_response_data = http_response_data()

    if opts.log_file:
        handler = logging.handlers.RotatingFileHandler(
            filename=opts.log_file,
            mode='a',
            maxBytes=1 * 1024 * 1024,
            backupCount=5
        )
        formatter = logging.Formatter(fmt='[%(asctime)s] %(levelname)s [%(name)s:%(lineno)03d] "%(message)s"')
        formatter.converter = time_converter
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.setLevel(logging.DEBUG)
    else:
        handler = logging.StreamHandler()
        formatter = logging.Formatter(fmt='[%(asctime)s] %(levelname)s [%(name)s:%(lineno)03d] "%(message)s"')
        formatter.converter = time_converter
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.setLevel(logging.DEBUG)

    t = threading.Thread(target=honeypot, args=(opts.local_ip, opts.local_port))
    t.setDaemon(True)
    t.start()

    try:
        while True:
            time.sleep(30)
    except KeyboardInterrupt:
        print '^C received, shutting down server'


if __name__ == '__main__':
    main()
